/*
 * Copyright 2003-2006 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.nanhill.commons.id.serial;

import org.nanhill.commons.id.AbstractStringIdentifierGenerator;



/**
 * <code>AlphanumericGenerator</code> is an identifier generator
 * that generates an incrementing number in base 36 as a String
 * object.
 *
 * <p>All generated ids have the same length (padding with 0's on the left),
 * which is determined by the <code>size</code> parameter passed to the constructor.<p>
 *
 * <p>The <code>wrap</code> property determines whether or not the sequence wraps
 * when it reaches the largest value that can be represented in <code>size</code>
 * base 36 digits. If <code>wrap</code> is false and the the maximum representable
 * value is exceeded, an IllegalStateException is thrown</p>
 *
 * @author Commons-Id team
 * @version $Id: AlphanumericGenerator.java,v 1.1 2006/09/20 06:56:00 alin Exp $
 */
public class AlphanumericGenerator extends AbstractStringIdentifierGenerator {

    /**
     * Should the counter wrap.
     */
    private boolean wrapping = true;

    /**
     * The counter.
     */
    private char[] count = null;

    /**
     * 'z' char
     */
    private static final char Z_CHAR = 'z';

    /**
     * '9' char
     */
    private static final char NINE_CHAR = '9';

    /**
     * Constructor.
     *
     * @param wrap should the factory wrap when it reaches the maximum
     *  long value (or throw an exception)
     * @param size  the size of the identifier
     */
    public AlphanumericGenerator(boolean wrap, int size) {
        super();
        this.wrapping = wrap;
        if (size < 1) {
            throw new IllegalArgumentException("The size must be at least one");
        }
        this.count = new char[size];
        for (int i = 0; i < size; i++) {
            count[i] = '0';  // zero
        }
    }

    /**
     * Construct with a counter, that will start at the specified
     * alphanumeric value.</p>
     *
     * @param wrap should the factory wrap when it reaches the maximum
     * value (or throw an exception)
     * @param initialValue the initial value to start at
     */
    public AlphanumericGenerator(boolean wrap, String initialValue) {
        super();
        this.wrapping = wrap;
        this.count = initialValue.toCharArray();

        for (int i = 0; i < this.count.length; i++) {
            char ch = this.count[i];
            if (ch >= '0' && ch <= '9') continue;
            if (ch >= 'a' && ch <= 'z') continue;
            
            throw new IllegalArgumentException(
                    "character " + this.count[i] + " is not valid");
        }
    }
    
    public long maxLength() {
        return this.count.length;
    }

    public long minLength() {
        return this.count.length;
    }

    /**
     * Getter for property wrap.
     *
     * @return <code>true</code> if this generator is set up to wrap.
     *
     */
    public boolean isWrap() {
        return wrapping;
    }

    /**
     * Sets the wrap property.
     *
     * @param wrap value for the wrap property
     *
     */
    public void setWrap(boolean wrap) {
        this.wrapping = wrap;
    }

    /**
     * Returns the (constant) size of the strings generated by this generator.
     *
     * @return the size of generated identifiers
     */
    public int getSize() {
        return this.count.length;
    }

    public synchronized String nextStringIdentifier() {
        for (int i = count.length - 1; i >= 0; i--) {
            switch (count[i]) {
                case Z_CHAR:  // z
                    if (i == 0 && !wrapping) {
                        throw new IllegalStateException
                        ("The maximum number of identifiers has been reached");
                    }
                    count[i] = '0';
                    break;

                case NINE_CHAR:  // 9
                    count[i] = 'a';
                    i = -1;
                    break;

                default:
                    count[i]++;
                    i = -1;
                    break;
            }
        }
        return new String(count);
    }
}
